/**
 * This file is Copyright © 2008 Software Craftsmen Limited. All Rights Reserved.
 */
package com.softwarecraftsmen.orogeny.filing.fileSystems;

import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectories;
import com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory;
import static com.softwarecraftsmen.orogeny.filing.AbsoluteDirectory.absoluteDirectory;
import com.softwarecraftsmen.orogeny.filing.AbsoluteFile;
import com.softwarecraftsmen.orogeny.filing.Directory;
import com.softwarecraftsmen.orogeny.filing.FileName;
import com.softwarecraftsmen.orogeny.filing.Folder;
import com.softwarecraftsmen.orogeny.filing.Folder.OrdinaryFolder;
import static com.softwarecraftsmen.orogeny.filing.Folder.folder;
import com.softwarecraftsmen.orogeny.filing.RelativeDirectory;
import static com.softwarecraftsmen.orogeny.filing.RelativeDirectory.relativeDirectory;
import static com.softwarecraftsmen.orogeny.filing.fileSystems.WindowsCaseInsensitiveFileSystem.C;
import static com.softwarecraftsmen.orogeny.filing.fileSystems.WindowsCaseInsensitiveFileSystem.windowsCaseInsensitiveFileSystem;
import com.softwarecraftsmen.orogeny.operatingSystems.OperatingSystemPlatform;
import static com.softwarecraftsmen.orogeny.operatingSystems.OperatingSystemPlatform.*;
import org.jetbrains.annotations.NotNull;

import java.io.File;
import static java.io.File.listRoots;
import java.io.StringWriter;
import static java.lang.System.getProperty;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public abstract class AbstractFileSystem implements FileSystem
{
	@NotNull
	public static final FileSystem CurrentFileSystem = chooseCurrentFileSystem();

	private static FileSystem chooseCurrentFileSystem()
	{
		final OperatingSystemPlatform operatingSystemPlatform = currentOperatingSystemPlatform();
		if (operatingSystemPlatform == Windows)
		{
			final String currentWorkingDirectory = getProperty("user.dir", null);
			if (currentWorkingDirectory == null || currentWorkingDirectory.length() < 2)
			{
				return C;
			}
			return windowsCaseInsensitiveFileSystem(currentWorkingDirectory.substring(0, 1));
		}
		else if (operatingSystemPlatform == Unsupported)
		{
			throw new IllegalStateException("Unsupported Operating System Family");
		}
		else if (operatingSystemPlatform == MacOSX)
		{
			return MacOSXHfsCaseRecordedFileSystem.MacOSXHfsRoot;
		}
		else
		{
			return PosixCaseSensitiveFileSystem.PosixRoot;
		}
	}

	private final String folderSeparator;
	private final String pathSeparator;
	private final String rootFolderName;

	protected AbstractFileSystem(final @NotNull String folderSeparator, final @NotNull String pathSeparator, final @NotNull String rootFolderName)
	{
		this.folderSeparator = folderSeparator;
		this.pathSeparator = pathSeparator;
		this.rootFolderName = rootFolderName;
	}

	@NotNull
	public String toString()
	{
		return rootFolderName();
	}

	@NotNull
	public final String folderSeparator()
	{
		return folderSeparator;
	}

	@NotNull
	public final String pathSeparator()
	{
		return pathSeparator;
	}

	@NotNull
	public final String rootFolderName()
	{
		return rootFolderName;
	}

	@NotNull
	public final AbsoluteDirectory root()
	{
		return absoluteDirectory(this, rootFolder());
	}

	@NotNull
	public final OrdinaryFolder rootFolder()
	{
		return new OrdinaryFolder(rootFolderName, true);
	}


	// TODO: Strictly speaking, Windows has both user-directory relative and drive relative paths. We do not support the later (C:../documents say)
	// TODO: Windows UNC Paths do not normally exist in the Directory env variable as they don't work!
	/**
	 * @param root             This would ordinarily be the current working directory if <code>pathsDescription</code> is the contents of the environment PATH variable
	 * @param pathsDescription This would ordinarily be the contents of the environment PATH variable
	 * @return AbsolutePaths
	 */
	@NotNull
	public AbsoluteDirectories toPaths(final @NotNull AbsoluteDirectory root, final @NotNull String pathsDescription)
	{
		// Crude and likely to break, ignoes quoted paths
		final String[] pathDescriptions = pathsDescription.split(pathSeparator());

		final Set<AbsoluteDirectory> absoluteDirectories = new LinkedHashSet<>();
		for (String pathDescription : pathDescriptions)
		{
			absoluteDirectories.add(toPath(root, pathDescription));
		}
		return new AbsoluteDirectories(absoluteDirectories);
	}

	@NotNull
	public AbsoluteDirectory toPath(final @NotNull AbsoluteDirectory root, final @NotNull String pathDescription)
	{
		return toPath(pathDescription).makeAbsolute(root);
	}

	@NotNull
	public AbsoluteFile toFile(final @NotNull AbsoluteDirectory root, final @NotNull String pathDescription)
	{
		final AbsoluteDirectory absoluteDirectory = toPath(pathDescription).makeAbsolute(root);
		final StringWriter writer = new StringWriter();
		absoluteDirectory.deepestFolder().serialise(writer);
		return new AbsoluteFile(absoluteDirectory.parent(), new FileName(writer.toString()));
	}

	@NotNull
	public Directory toPath(final @NotNull String pathDescription)
	{
		final List<String> folderDescriptions = asList(pathDescription.split("\\" + folderSeparator()));
		if (folderDescriptions.size() == 0)
		{
			return relativeDirectory();
		}
		final String firstFolderDescription = folderDescriptions.get(0);

		final boolean isAbsolute = isAbsolute(firstFolderDescription);
		if (isAbsolute && folderDescriptions.size() == 1)
		{
			return root();
		}

		boolean hasAllOrdinaryFolders = true;
		final List<Folder> allFolders = new ArrayList<>();
		for (int index = (isAbsolute) ? 1 : 0; index < folderDescriptions.size(); index++)
		{
			final Folder folder = folder(folderDescriptions.get(index));
			hasAllOrdinaryFolders &= folder.isOrdinary();
			allFolders.add(folder);
		}
		if (isAbsolute)
		{
			if (hasAllOrdinaryFolders)
			{
				return AbsoluteDirectory.absoluteDirectory(actualFileSystem(firstFolderDescription), allFolders);
			}
			else
			{
				return new RelativeDirectory(allFolders).makeAbsolute(actualFileSystem(firstFolderDescription).root());
			}
		}
		else
		{
			return new RelativeDirectory(allFolders);
		}
	}

	@NotNull
	public String toFileSystemSpecificPathPrefixedByPathSeparator(final @NotNull List<? extends Folder> folders)
	{
		return pathSeparator() + toFileSystemSpecificPath(folders);
	}

	@NotNull
	public String toFileSystemSpecificPathPrefixedByPathSeparator(final @NotNull List<? extends Folder> folders, final @NotNull String fileName)
	{
		return pathSeparator() + toFileSystemSpecificPathPostfixedByFolderSeparator(folders) + fileName;
	}

	@NotNull
	public String toFileSystemSpecificPathPostfixedByFolderSeparator(final @NotNull List<? extends Folder> folders)
	{
		return toFileSystemSpecificPath(folders) + folderSeparator();
	}

	@NotNull
	public String toFileSystemSpecificPathPostfixedByPathSeparator(final @NotNull List<? extends Folder> folders)
	{
		return toFileSystemSpecificPath(folders) + pathSeparator();
	}

	@NotNull
	public String toFileSystemSpecificPathPostfixedByPathSeparator(final @NotNull List<? extends Folder> folders, final @NotNull String fileName)
	{
		return toFileSystemSpecificPathPostfixedByFolderSeparator(folders) + fileName + pathSeparator();
	}

	@NotNull
	public String toFileSystemSpecificPath(final @NotNull List<? extends Folder> folders)
	{
		final StringWriter writer = new StringWriter();
		boolean afterFirst = false;
		for (Folder folder : folders)
		{
			if (afterFirst)
			{
				writer.write(folderSeparator());
			}
			writer.write(folder.toString());
			afterFirst = true;
		}
		return writer.toString();
	}

	@NotNull
	public String toFileSystemSpecificPath(final @NotNull List<? extends Folder> folders, final @NotNull String fileName)
	{
		return toFileSystemSpecificPathPostfixedByFolderSeparator(folders) + fileName;
	}

	public boolean isHidden(final @NotNull List<? extends Folder> folders)
	{
		if (folders.size() > 0)
		{
			if (folders.get(folders.size() - 1).isOrdinaryAndPrefixedWithPeriod())
			{
				return true;
			}
		}
		return false;
	}

	public boolean isHidden(final @NotNull List<? extends Folder> folders, final @NotNull String fileName)
	{
		final String filePath = toFileSystemSpecificPath(folders, fileName);
		return new File(filePath).isHidden();
	}

	// TODO: Or currentFolder.getParentFile() == null;
	public boolean isRoot(final @NotNull File currentFolder)
	{
		for (File root : listRoots())
		{
			if (root.getAbsolutePath().equalsIgnoreCase(currentFolder.getAbsolutePath()))
			{
				return true;
			}
		}
		return false;
	}

	protected abstract boolean isAbsolute(final @NotNull String firstFolderDescription);

	@NotNull
	protected abstract FileSystem actualFileSystem(final @NotNull String firstFolderDescription);
}
